/*
 * $Log: Player.java,v $ Revision 1.6 2006/11/05 13:58:00 wetzker more merging -
 * CLang + MF + some states still missing
 * 
 * Revision 1.5.22.2 2006/10/25 14:33:35 endert Latest SoccerScope Version plus
 * Database ability
 * 
 * Revision 1.5.8.1 2006/06/26 07:04:23 benjaboe Datamodell umgebaut
 * 
 * Revision 1.5 2006/04/18 22:46:31 wetzker merged with situationEvaluation
 * branch new: - central situation evaluation for states with ball control -
 * kickNRun situation - ball interception possible when ball kickable - role
 * model for players 6-11(roles are shown in gui, roles not used much for acting
 * yet)
 * 
 * Revision 1.4 2006/04/18 00:28:01 endert PointTo Ready, OpponentAnalyzer fixed
 * and GUI updated!
 * 
 * Revision 1.3.2.1 2006/04/18 21:59:35 wetzker added role model for players
 * 6-11 improved evaluation function
 * 
 * Revision 1.3 2006/04/05 22:48:00 wetzker added prediction of other players
 * state each player now contains a stateEvaluation (see StateTactic) used the
 * prediction for calculation the players of the next cycle (can be seen in the
 * gui) + new State (OpponentBallInterception) for prediction of opponents
 * removed bug in delayed intercetion (cause stamina loss for nothing)
 * meta_playmodes can be used now (added always playmode) improved attack in
 * line
 * 
 * Revision 1.2 2006/03/26 17:37:27 wetzker hope to finally got the tackle area
 * displayed right
 * 
 * Revision 1.1 2006/03/13 07:37:46 axle moved from ROOT/src for Maven 2
 * Buildsystem and Team/Soccerscope separation
 * 
 * Revision 1.8 2006/03/07 12:46:00 endert Merge after refactoring: - New
 * State-Interface: One Method for each action type - Action Bean removed -
 * ActionFactorys for helper methods, States calculate main-Action themselves -
 * formatted code - added State-List in Monitor (Context-Menu of Player) - other
 * smaller stuff
 * 
 * Revision 1.7.2.2 2006/03/06 23:17:53 endert Removed warnings, added
 * State-Overview to Context-Menu!
 * 
 * Revision 1.7.2.1 2006/02/25 22:07:25 wetzker formated
 * 
 * Revision 1.7 2006/02/22 16:04:42 endert This is done: - Rebound SoccerScope
 * to Agents (can be added or removed in config-file) - Situations now all
 * extend abstract class Situation - Refactorings: ActionBean _ removed from
 * Attributes - SoccerScope will automatically connect to host given in
 * Start-Call - Trainer uses the same host as soccerscope - Removed unused stuff
 * (old confs, bat, etc.) - Code formatted (good luck when merging) - organized
 * imports - all that i forgot to mention here
 * 
 * Revision 1.6 2005/10/25 20:51:37 endert Added Training-Editor to SoccerScope,
 * restructuring in some old classes and some experimental stuff (goals,
 * commitments, mental model)
 * 
 * Revision 1.5 2005/10/14 14:58:15 endert Now, say-messages are visualized
 * inside in SoccerScope. However, the information comes from the RobocupAgent
 * directly, using the SoccerScope.getInstance() method (in GUI-Mode only), so
 * that it is a work-around. The message itself can be seen inside the
 * "Property"-Menu of the Players Context-Menu, as well as any heared PSI,
 * whereas saying is visualized additionally on the layer (String removed, was
 * to big! Uncomment the drawStrings in SayLayer, to revisualize them).
 * 
 * Revision 1.4 2005/08/16 16:26:47 wetzker merged composite branch
 * 
 * Revision 1.3.4.1 2005/07/14 16:53:54 wetzker + speed in AgentWM is displayed
 * 
 * Revision 1.3 2005/06/20 21:57:48 andpaul added: store and show reliable flag
 * 
 * Revision 1.2 2005/06/19 20:22:39 till changed Player() to Player(int
 * teamSide)
 * 
 * Revision 1.1 2005/04/20 10:39:02 endert Fresh Team Repository installation
 * from /home/e/endert/Robocup to here.
 * 
 * Revision 1.5 2005/03/13 15:08:22 haeder * some modifications to reduce the
 * number of warnings ;-) - removed unreadable comments
 * 
 * Revision 1.4 2005/02/06 11:55:55 haeder commented out illegal imports
 * 
 * Revision 1.3 2005/02/06 07:17:26 maketi Added soccerscope with connection to
 * our world model
 * 
 * Revision 1.10 2003/05/30 04:56:00 newone *** empty log message ***
 * 
 * Revision 1.9 2003/01/04 08:34:38 koji
 * {kick,dash,turn}������Ȥ��ɤ߹���褦�˽�5
 * 
 * Revision 1.8 2002/10/17 09:32:46 koji canTackle�ν�5
 * 
 * Revision 1.7 2002/10/10 09:43:32 koji ͥ*�ΰ�η׻��β���
 * (Direction-Distance Table)
 * 
 * Revision 1.6 2002/10/04 10:39:06 koji
 * Tuple,Point,Vector�Υѥå������ѹ���Geometry,Circle,Line,Rectangle�ɲ�
 * 
 * Revision 1.5 2002/10/01 14:11:02 koji support Preference, gif format
 * 
 * Revision 1.4 2002/09/12 17:07:03 koji
 * HeteroParam�d�ä��ѹ���Hetro->Hetero
 * 
 * Revision 1.3 2002/09/05 10:20:19 taku-ako �إƥ�ץ쥤�䡼���б�
 * 
 * Revision 1.2 2002/06/04 05:58:09 koji Tackle�θ�̤�ɽ��
 * 
 * Revision 1.1.1.1 2002/03/01 14:12:54 koji CVS�����
 * 
 */

package soccerscope.model;

// import robocup.component.worldmodel.WorldModelChangeEvent;
// import robocup.component.worldmodel.WorldModelChangeListener;
import java.util.LinkedList;

import soccerscope.util.geom.Geometry;
import soccerscope.util.geom.Line2f;
import soccerscope.util.geom.Point2f;
import soccerscope.util.geom.Vector2f;

public class Player
    extends SoccerObject {

  /**
   * 
   */
  private static final long serialVersionUID = 4237867971705547820L;

  // player mode
  public final static int   DISABLE          = 0x0000;

  public final static int   STAND            = 0x0001;

  public final static int   KICK             = 0x0002;

  public final static int   KICK_FAULT       = 0x0004;

  public final static int   GOALIE           = 0x0008;

  public final static int   CATCH            = 0x0010;

  public final static int   CATCH_FAULT      = 0x0020;

  public final static int   BALL_TO_PLAYER   = 0x0040;

  public final static int   PLAYER_TO_BALL   = 0x0080;

  public final static int   DISCARD          = 0x0100;

  public final static int   LOST             = 0x0200;

  public final static int   BALL_COLLIDE     = 0x0400;

  public final static int   PLAYER_COLLIDE   = 0x0800;

  public final static int   TACKLE           = 0x1000;

  public final static int   TACKLE_FAULT     = 0x2000;

  public final static int   BACK_PASS        = 0x4000;

  public final static int   FREE_KICK_FAULT  = 0x8000;

  // player type
  public final static int   PT_NULL          = 0;

  public final static int   PT_DEFAULT       = 0;

  public final static int   PT_HETRO_A       = 1;

  public final static int   PT_HETRO_B       = 2;

  public final static int   PT_HETRO_C       = 3;

  public final static int   PT_HETRO_D       = 4;

  public final static int   PT_HETRO_E       = 5;

  public final static int   PT_HETRO_F       = 6;

  // view quality
  public final static int   LOW              = 0;

  public final static int   HIGH             = 1;

  // error direction
  public final static int   ERROR_DIR        = 1024;

  /** The WorldModel of the player if he has transmit this data. */
  private PlayerWorldModel  worldModel;

  public boolean            reliable         = true;

  public int                side;

  public int                unum;

  public int                stamina;

  public float              angle;                                  // degree

  public int                angleNeck;                              // degree

  public int                angleVisible;                           // degree

  public int                viewQuality;

  public String             sayMessage;

  public LinkedList<String> hearMessage;

  public float              catchDir;

  public boolean            offside;

  public int                mode;

  public int                type;

  public int                playerType;

  public int                interceptionTime;

  public int                roleNumber       = -1;

  /*
   * public double effort; public double recovery;
   */
  public int                kickCount;

  public int                dashCount;

  public int                turnCount;

  public int                sayCount;

  public int                turnNeckCount;

  public int                catchCount;

  public int                moveCount;

  public int                changeViewCount;

  public String             state            = "";

  public Player(
      int teamSide) {

    super();
    this.side = teamSide;
    this.unum = 0;
    this.stamina = 0;
    this.angle = 0;
    this.angleNeck = 0;
    this.angleVisible = 90;
    this.viewQuality = Player.HIGH;
    this.sayMessage = "";
    this.hearMessage = new LinkedList<String>();
    this.catchDir = Player.ERROR_DIR;
    this.offside = false;
    this.mode = Player.DISABLE;
    this.type = Player.PT_DEFAULT;
    this.playerType = 0;

    /*
     * effort = 1.0; recovery = 1.0;
     */
    this.kickCount = 0;
    this.dashCount = 0;
    this.sayCount = 0;
    this.turnNeckCount = 0;
    this.catchCount = 0;
    this.changeViewCount = 0;

  }

  public void setPlayer(Player p) {

    this.pos.set(p.pos);
    this.vel.set(p.vel);
    this.acc.set(p.acc);
    this.side = p.side;
    this.unum = p.unum;
    this.stamina = p.stamina;
    this.angle = p.angle;
    this.angleNeck = p.angleNeck;
    this.angleVisible = p.angleVisible;
    this.viewQuality = p.viewQuality;
    this.sayMessage = p.sayMessage;
    this.hearMessage = p.hearMessage;
    this.catchDir = p.catchDir;
    this.offside = p.offside;
    this.mode = p.mode;
    this.type = p.type;
    this.playerType = p.playerType;
    this.roleNumber = p.roleNumber;
  }

  public boolean isEnable() {

    return ((this.mode & Player.STAND) == Player.STAND);
  }

  public boolean isGoalie() {

    return ((this.mode & Player.GOALIE) == Player.GOALIE);
  }

  public boolean isKicking() {

    return ((this.mode & Player.KICK) == Player.KICK);
  }

  public boolean isKickFault() {

    return ((this.mode & Player.KICK_FAULT) == Player.KICK_FAULT);
  }

  public boolean isCatching() {

    return ((this.mode & Player.CATCH) == Player.CATCH);
  }

  public boolean isCatchFault() {

    return ((this.mode & Player.CATCH_FAULT) == Player.CATCH_FAULT);
  }

  public boolean isCollision() {

    return ((this.mode & Player.BALL_COLLIDE) == Player.BALL_COLLIDE) || ((this.mode & Player.PLAYER_COLLIDE) == Player.PLAYER_COLLIDE);
  }

  public boolean isTackling() {

    return ((this.mode & Player.TACKLE) == Player.TACKLE);
  }

  public boolean isTackleFault() {

    return ((this.mode & Player.TACKLE_FAULT) == Player.TACKLE_FAULT);
  }

  @Override
  public float getSize() {

    return HeteroParam.get(this.type).player_size;
  }

  @Override
  public java.awt.Color getColor() {

    switch (this.side) {
      case Team.LEFT_SIDE:
        if (this.isGoalie()) {
          return ColorDB.getColor("goalie_l_color");
        }
        return ColorDB.getColor("team_l_color");
      case Team.NEUTRAL:
        if (this.isGoalie()) {
          return ColorDB.getColor("nuetral_goalie");
        }
        return ColorDB.getColor("nuetral_player");
      case Team.RIGHT_SIDE:
        if (this.isGoalie()) {
          return ColorDB.getColor("goalie_r_color");
        }
        return ColorDB.getColor("team_r_color");
    }
    return java.awt.Color.white;
  }

  public boolean isOffside(float line) {

    return this.side == Team.LEFT_SIDE && this.pos.x > line || this.side == Team.RIGHT_SIDE && this.pos.x < line;
  }

  public boolean isKickable(Point2f ball) {

    return this.pos.distance(ball) <= (Param.BALL_SIZE + this.getSize() + HeteroParam
        .get(this.type).kickable_margin);
  }

  public boolean isCatchable(Point2f ball) {

    return this.pos.distance(ball) <= Param.GOALIE_CATCHABLE_AREA_LENGTH;
  }

  public boolean canTackle(Point2f ball) {

    Vector2f rb = this.toRelative(ball);
    return (rb.x >= -Param.TACKLE_BACK_DIST && rb.x <= Param.TACKLE_DIST && Math
        .abs(rb.y) <= Param.TACKLE_WIDTH / 2);
  }

  public void step() {

    this.pos.add(this.vel);
    this.pos.add(this.acc);
    this.vel.add(this.acc);
    this.vel.scale(HeteroParam.get(this.type).player_decay);
    this.acc.set(0, 0);
  }

  public float getKickable() {

    return (Param.BALL_SIZE + this.getSize() + HeteroParam.get(this.type).kickable_margin);
  }

  public float getMoveMax() {

    return (1 + Param.PLAYER_RAND) * HeteroParam.get(this.type).player_speed_max;
  }

  public float getTurnAngle() {

    return 180 / (1 + HeteroParam.get(this.type).inertia_moment * this.vel.r());
  }

  public float getDashAccelerationMax() {

    return HeteroParam.get(this.type).dash_power_rate * HeteroParam
        .get(this.type).effort_max * Param.POWER_MAX;
  }

  public Vector2f getVector() {

    return Vector2f.polar2vector(this.getSize(), this.angle);
  }

  public Line2f getLine() {

    Point2f t = new Point2f(this.pos);
    t.add(this.getVector());
    return new Line2f(this.pos, t);
  }

  public Vector2f toRelative(Point2f p) {

    return new Vector2f(this.pos, p).rotate(-this.angle);
  }

  public float getAngleFromBody(Point2f p) {

    return Geometry.normalizeAngle(this.pos.dir(p) - this.angle);
  }

  public float getAngleFromNeck(Point2f p) {

    return Geometry
        .normalizeAngle(this.pos.dir(p) - this.angle - this.angleNeck);
  }

  public Vector2f getDashAccelerate(float power) {

    // effort == 1 �Ȥ��Ʒ׻�
    return Vector2f.polar2vector(
        HeteroParam.get(this.type).dash_power_rate * power, this.angle);
  }

  public Vector2f getKickAccelerate(Point2f ball,
      float power,
      float dir) {

    Vector2f diff = new Vector2f();
    diff.sub(ball, this.pos);
    float effectiveKickPowerRate = Param.KICK_POWER_RATE;
    effectiveKickPowerRate *= 1.0 - (diff.length() - Param.BALL_SIZE - this
        .getSize()) / HeteroParam.get(this.type).kickable_margin * 0.25 - Math
        .abs(Geometry.normalizeAngle(diff.th() - this.angle)) / 180.0 * 0.25;
    return Vector2f.polar2vector(effectiveKickPowerRate * power, Geometry
        .normalizeAngle(this.angle + dir));
  }

  public Vector2f getFinalMove() {

    return Vector2f.polar2vector(this.calcFinalDistance(), this.vel.th());
  }

  public float getTackleProb(Point2f p) {

    Vector2f tmp = this.toRelative(p);
    float prob = (float) (1.0 - (Math
        .pow(
            (tmp.x > 0) ? tmp.x / Param.TACKLE_DIST : -tmp.x / Param.TACKLE_BACK_DIST,
            Param.TACKLE_EXPONENT) + Math.pow(
        Math.abs(tmp.y) / (Param.TACKLE_WIDTH / 2.0f), Param.TACKLE_EXPONENT)));
    return prob;
  }

  // (a == 0)(t >= 0)
  public float calcVelocityAfterNTime(int t) {

    float v = this.vel.r();
    float d = HeteroParam.get(this.type).player_decay;
    return v * (float) Math.pow(d, t);
  }

  // (a == 0)(t >= 0)
  public float calcDistanceAfterNTime(int t) {

    float v = this.vel.r();
    float d = HeteroParam.get(this.type).player_decay;
    return v * (1 - (float) Math.pow(d, t)) / (1 - d);
  }

  // (a != 0)(t >= 0)
  public float calcVelocityAfterNTime(float a,
      int t) {

    float d = HeteroParam.get(this.type).player_decay;
    return this.calcVelocityAfterNTime(t) + a * ((1 - (float) Math
        .pow(d, t + 1)) / (1 - d) - 1);
  }

  // (a != 0)(t >= 0)
  public float calcDistanceAfterNTime(float a,
      int t) {

    float d = HeteroParam.get(this.type).player_decay;
    return this.calcDistanceAfterNTime(t) + (a / (1 - d)) * (t + 1 - (1 - (float) Math
        .pow(d, t + 1)) / (1 - d));
  }

  // (t -> ��)(a != 0)
  public float calcFinalVelocity(float a) {

    float d = HeteroParam.get(this.type).player_decay;
    return a * d / (1 - d);
  }

  // (t -> ��)(a == 0)
  public float calcFinalDistance() {

    float v = this.vel.r();
    float d = HeteroParam.get(this.type).player_decay;
    return v / (1 - d);
  }

  // (a == 0)(vt < v)
  public int calcTimeByVelocity(float vt) {

    // float v = this.vel.r();
    float d = HeteroParam.get(this.type).player_decay;
    return (int) Math
        .ceil(((float) Math.log(vt) / (float) Math.log(d)) - ((float) Math
            .log(this.vel.r()) / (float) Math.log(d)));
  }

  // (a == 0)(dist >= 0)
  public int calcTimeByDistance(float dist) {

    float v = this.vel.r();
    float d = HeteroParam.get(this.type).player_decay;
    return (int) Math
        .ceil(((float) Math.log(1 - dist * (1 - d) / v)) / ((float) Math.log(d)));
  }

  // (a != 0)(max(a, v) < vt <= speed_max)
  public int calcTimeByVelocity(float vt,
      float a) {

    float v = this.vel.r();
    float d = HeteroParam.get(this.type).player_decay;
    return (int) Math.ceil(((float) Math
        .log((vt - a / (1 - d) + a) / (v / d - a / (1 - d))) / (float) Math
        .log(d)) - 1);
  }

  // (a != 0)(dist >= 0)
  public int calcTimeByDistance(float dist,
      float a) {

    int time = (int) dist;
    float tmp_dist = this.calcDistanceAfterNTime(a, time);
    if (tmp_dist == dist) {
      return time;
    }
    else if (tmp_dist < dist) {
      while (tmp_dist < dist) {
        time++;
        tmp_dist = this.calcDistanceAfterNTime(a, time);
      }
    }
    else {
      while (tmp_dist > dist) {
        time--;
        tmp_dist = this.calcDistanceAfterNTime(a, time);
      }
      time++;
    }
    return time;
  }

  // (a != 0)(dist >= 0)
  public int estimateMinTimeByDistance(float dist,
      float a) {

    float d = HeteroParam.get(this.type).player_decay;
    return (int) Math.ceil(dist / (a + (a * d) / (1 - d)));
  }

  public static void main(String arg[]) {

    Player p = new Player(Team.NEUTRAL);
    float acc = p.getDashAccelerationMax();

    long time, diff;

    time = System.currentTimeMillis();
    for (int i = 0; i < 22; i++) {
      for (float d = 0.0f; d < 100; d += 0.5f) {
        p.calcTimeByDistance(d, acc);
      }
    }
    diff = System.currentTimeMillis();
    System.out.println("normal: " + (diff - time));

    time = diff;
    float table[] = new float[120];
    for (int i = 0; i < 120; i++) {
      table[i] = p.calcDistanceAfterNTime(acc, i);
    }
    diff = System.currentTimeMillis();
    System.out.println("make: " + (diff - time));

    time = diff;
    for (int i = 0; i < 22; i++) {
      for (float d = 0.0f; d < 100; d += 0.5f) {
        p.search(table, d);
      }
    }
    diff = System.currentTimeMillis();
    System.out.println("cache: " + (diff - time));
  }

  public int search(float[] t,
      float key) {

    int idx = (int) key;
    float tmp = t[idx];
    if (tmp == key) {
      return idx;
    }
    else if (tmp < key) {
      while (tmp < key) {
        idx++;
        tmp = t[idx];
      }
    }
    else {
      while (tmp > key) {
        idx--;
        tmp = t[idx];
      }
      idx++;
    }
    return idx;
  }

  @Override
  public String toString() {

    return "Player{" + "side=" + this.side + ", unum=" + this.unum + ", stamina=" + this.stamina + ", angle=" + this.angle + ", angleNeck=" + this.angleNeck + ", angleVisible=" + this.angleVisible + ", viewQuality=" + this.viewQuality + ", sayMessage='" + this.sayMessage + "'" + ", catchDir=" + this.catchDir + ", offside=" + this.offside + ", mode=" + this.mode + ", type=" + this.type + ", kickCount=" + this.kickCount + ", dashCount=" + this.dashCount + ", turnCount=" + this.turnCount + ", sayCount=" + this.sayCount + ", turnNeckCount=" + this.turnNeckCount + ", catchCount=" + this.catchCount + ", moveCount=" + this.moveCount + ", changeViewCount=" + this.changeViewCount + "}";
  }

  @Override
  public void setPosition(Point2f pos) {

    super.setPosition(pos);
  }

  /**
   * Return the WorldModel of the player.
   * 
   * @return the WorldModel or null if the player has not transmit it
   */
  public PlayerWorldModel getWorldModel() {

    return this.worldModel;
  }

  /**
   * Sets the WorldModel of the player.
   * 
   * @param worldModel
   *          the WorldModel of the player he has transmit
   */
  public void setWorldModel(PlayerWorldModel worldModel) {

    this.worldModel = worldModel;
  }

}
